package org.openmrs.web.services.client;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;
import javax.net.ssl.SSLSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.web.WebConstants;

public class WebServiceClient {
	
    /** Logger for this class and subclasses */
    protected final Log log = LogFactory.getLog(getClass());

	private String wsdlUrl;
	private String serviceNamespace;
	private String servicePortName;
	private String serviceUrl;
	private List<String> errorCodes;
	private Dispatch dispatch;

	public String toString() {
		String ret = this.getClass().getName();
		
		if ( this.wsdlUrl != null ) {
			ret += " [wsdl:" + this.wsdlUrl + "]";
		}
		if ( this.serviceNamespace != null ) {
			ret += " [ns:" + this.serviceNamespace + "]";
		}
		if ( this.servicePortName != null ) {
			ret += " [port:" + this.servicePortName + "]";
		}
		if ( this.serviceUrl != null ) {
			ret += " [url:" + this.serviceUrl + "]";
		}
		
		return ret;
	}
	
	@SuppressWarnings("unchecked")
	public Object getResponse(Object requestObj, Class responseObjClass) {
		Object ret = null;
		
		if ( this.wsdlUrl != null && this.serviceNamespace != null && this.servicePortName != null 
				&& this.serviceUrl != null && requestObj != null && responseObjClass != null ) {

			log.debug("In getResponse() method with [" + this.wsdlUrl + "] [" + this.serviceNamespace + "] [ " + this.servicePortName
					+ "] [" + this.serviceUrl + "] [" + requestObj.getClass().getName() + "] [" + responseObjClass.getName() + "]");
			
			//System.setProperty("java.protocol.handler.pkgs", pkgname);
			System.setProperty("javax.net.ssl.keyStore", WebConstants.OPENMRS_KEYSTORE);
			System.setProperty("javax.net.ssl.keyStorePassword", WebConstants.OPENMRS_KEYSTORE_PASSWORD);
			System.setProperty("javax.net.ssl.trustStore", WebConstants.OPENMRS_KEYSTORE);
			System.setProperty("javax.net.ssl.truststorePassword", WebConstants.OPENMRS_KEYSTORE_PASSWORD);
			
			// Make sure URL is verified through SSL...
			HostnameVerifier hv = new HostnameVerifier() {
			    public boolean verify(String urlHostName, SSLSession session) {
			        System.out.println("Warning: URL Host: "+ urlHostName+ " vs. " + session.getPeerHost());
			        return true;
			    }
			};
			 
			//HttpsURLConnection.setDefaultHostnameVerifier(hv);

			Service service = null;
			try {
				service = Service.create(new URL(this.wsdlUrl), new QName(this.serviceNamespace));
			} catch ( MalformedURLException mue ) {
				service = null;
				log.debug("Failed to create service: ", mue);
				addErrorCode("Webservice.client.connection.malformedUrl");
			}

			if ( service != null ) {
				service.addPort(new QName(this.servicePortName), SOAPBinding.SOAP11HTTP_BINDING, this.serviceUrl);

				//((BindingProvider)proxy).getRequestContext().put(BindingProvider.SESSION_MAINTAIN_PROPERTY,true);
				
				Class[] jaxbClasses = {Concept.class, requestObj.getClass(), responseObjClass};
				if ( dispatch == null ) {
					log.debug("CREATING NEW DISPATCH");
					try {
						dispatch = service.createDispatch(new QName(this.servicePortName), JAXBContext.newInstance(jaxbClasses), Service.Mode.PAYLOAD);
						dispatch.getRequestContext().put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);
					} catch ( JAXBException je ) {
						log.debug("Failed to create dispatch: ", je);
						dispatch = null;
						addErrorCode("Webservice.client.connection.jaxb.cantCreateContext");
					}
				} else {
					log.debug("USING OLD DISPATCH - HOPEFULLY MAINTAIN SESSION");
					log.debug("maintain is " + dispatch.getRequestContext().get(BindingProvider.SESSION_MAINTAIN_PROPERTY));
					try {
						dispatch = service.createDispatch(new QName(this.servicePortName), JAXBContext.newInstance(jaxbClasses), Service.Mode.PAYLOAD);
						log.debug("and then maintain is " + dispatch.getRequestContext().get(BindingProvider.SESSION_MAINTAIN_PROPERTY));
						dispatch.getRequestContext().put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);
						log.debug("and finally maintain is " + dispatch.getRequestContext().get(BindingProvider.SESSION_MAINTAIN_PROPERTY));
					} catch ( JAXBException je ) {
						log.debug("Failed to create dispatch: ", je);
						dispatch = null;
						addErrorCode("Webservice.client.connection.jaxb.cantCreateContext");
					}
				}
				
				if ( dispatch != null ) {
					try { 
						ret = dispatch.invoke(requestObj);
					} catch ( Exception e ) {
						log.debug("Failed to invoke dispatch: " + e);
					}
				} else {
					addErrorCode("Webservice.client.connection.cantCreateDispatch");
				}
			} else {
				addErrorCode("Webservice.client.connection.cantCreateService");
			}
		} else {
			if ( this.wsdlUrl == null ) addErrorCode("Webservice.client.connection.nullWsdlUrl");
			if ( this.serviceNamespace == null ) addErrorCode("Webservice.client.connection.nullServiceNamespace");
			if ( this.serviceUrl == null ) addErrorCode("Webservice.client.connection.nullServiceName");
			if ( this.servicePortName == null ) addErrorCode("Webservice.client.connection.nullPortName");
			if ( responseObjClass == null ) addErrorCode("Webservice.client.connection.nullResponseClass");
			if ( requestObj == null ) addErrorCode("Webservice.client.connection.nullRequestObj");
		}
		
		return ret;
	}
	
	private void addErrorCode(String errorCode) {
		if ( this.errorCodes == null ) errorCodes = new ArrayList<String>();
		errorCodes.add(errorCode);
	}

	/**
	 * @return Returns the serviceNamespace.
	 */
	public String getServiceNamespace() {
		return serviceNamespace;
	}

	/**
	 * @param serviceNamespace The serviceNamespace to set.
	 */
	public void setServiceNamespace(String serviceNamespace) {
		this.serviceNamespace = serviceNamespace;
	}

	/**
	 * @return Returns the servicePortName.
	 */
	public String getServicePortName() {
		return servicePortName;
	}

	/**
	 * @param servicePortName The servicePortName to set.
	 */
	public void setServicePortName(String servicePortName) {
		this.servicePortName = servicePortName;
	}

	/**
	 * @return Returns the serviceUrl.
	 */
	public String getServiceUrl() {
		return serviceUrl;
	}

	/**
	 * @param serviceUrl The serviceUrl to set.
	 */
	public void setServiceUrl(String serviceUrl) {
		this.serviceUrl = serviceUrl;
	}

	/**
	 * @return Returns the wsdlUrl.
	 */
	public String getWsdlUrl() {
		return wsdlUrl;
	}

	/**
	 * @param wsdlUrl The wsdlUrl to set.
	 */
	public void setWsdlUrl(String wsdlUrl) {
		this.wsdlUrl = wsdlUrl;
	}

	/**
	 * @return Returns the errorCodes.
	 */
	public List<String> getErrorCodes() {
		return errorCodes;
	}
	
	

}
